﻿using System;
using System.Linq;
using System.Windows;
using System.Windows.Media.Animation;

namespace Comarch.Animation
{
    public partial class StoryboardBuilder
    {
        private TimeSpan _seekTime;
        public Point RenderTransformOrigin { get; set; }
        public Storyboard Storyboard { get; private set; }
        
        public int DefaultDuration = 300;
        public StoryboardBuilder SetDefaultDuration(int duration)
        {
            DefaultDuration = duration;
            return this;
        }

        public  IEasingFunction DefaultEasingFunction = new CubicEase();
        public StoryboardBuilder SetDefaultEasingFunction(IEasingFunction easingFunction)
        {
            DefaultEasingFunction = easingFunction;
            return this;
        }

        private EntranceEffects _entranceEffects;
        public EntranceEffects EntranceEffects { get { return _entranceEffects ?? (_entranceEffects = new EntranceEffects(this)); } }

        private EmphasisEffects _emphasisEffects;
        public EmphasisEffects EmphasisEffects
        {
            get
            {
                if (_emphasisEffects == null)
                    _emphasisEffects = new EmphasisEffects(this);
                return _emphasisEffects;
            }
            set
            { _emphasisEffects = value; }
        }

        private ExitEffects _exitEffects;
        public ExitEffects ExitEffects
        {
            get
            {
                if (_exitEffects == null)
                    _exitEffects = new ExitEffects(this);
                return _exitEffects;
            }
            set
            { _exitEffects = value; }
        }

        public StoryboardBuilder()
        {
            Storyboard = new Storyboard();
        }

        public StoryboardBuilder AddDoubleKeyFrames(DependencyObject target, string targetProperty, double to, bool isRelativeTransform)
        {
            return AddDoubleKeyFrames(target, targetProperty, to, DefaultDuration, isRelativeTransform);
        }

        public StoryboardBuilder AddDoubleKeyFrames(DependencyObject target, string targetProperty, double to, int duration, bool isRelativeTransform)
        {
            return AddDoubleKeyFrames(target, targetProperty, to, duration, DefaultEasingFunction, isRelativeTransform);
        }

        public StoryboardBuilder AddDoubleKeyFrames(DependencyObject target, string targetProperty, double to, int duration, IEasingFunction easingFunction, bool isRelativeTransform)
        {
            var animationLine = GetAnimationLine(targetProperty, target);
            AddAnimation(animationLine, target, targetProperty, duration, to, easingFunction, isRelativeTransform);
            return this;
        }

        private void AddAnimation(DoubleAnimationUsingKeyFrames animation, DependencyObject target, string targetProperty, int duration, double to, IEasingFunction easingfunction, bool isRelativeTransform)
        {
            var previousValue = GetPreviousValue(animation, target, targetProperty);
            var newValue = GetNewValue(previousValue, to, isRelativeTransform);
            var max = _seekTime + TimeSpan.FromMilliseconds(duration);

            animation.KeyFrames.Add(new EasingDoubleKeyFrame { KeyTime = _seekTime, Value = previousValue });
            animation.KeyFrames.Add(new EasingDoubleKeyFrame { KeyTime = max, Value = newValue, EasingFunction = easingfunction });

            if (!animation.Duration.HasTimeSpan || animation.Duration < max)
                animation.Duration = max;

            if (!Storyboard.Duration.HasTimeSpan || Storyboard.Duration < max)
                Storyboard.Duration = max;
        }

        private static double GetNewValue(double previousValue, double to, bool isRelativeTransform)
        {
            if (isRelativeTransform)
                return to + previousValue;
            return to;
        }

        private double GetPreviousValue(DoubleAnimationUsingKeyFrames animation, DependencyObject target, string targetProperty)
        {
            var lastKeyFrameValue = animation.KeyFrames.Count == 0 ? (double?)null : animation.KeyFrames.Cast<DoubleKeyFrame>().Where(x => x.KeyTime.TimeSpan <= _seekTime).Last().Value;
            return lastKeyFrameValue ?? (double) target.GetValue(targetProperty);
        }

        private DoubleAnimationUsingKeyFrames GetAnimationLine(string targetProperty, DependencyObject target)
        {
            var animation = Storyboard.Children.OfType<DoubleAnimationUsingKeyFrames>().FirstOrDefault(
                x => Storyboard.GetTargetProperty(x).Path.Equals(targetProperty) && Storyboard.GetTargetName(x).Equals(target.GetHashCode().ToString()));

            // TODO: Remove GetHashCode()
            if (animation == null)
            {
                animation = new DoubleAnimationUsingKeyFrames();
                Storyboard.SetTarget(animation, target);
#if SL5
             //   StoryboardServices.SetTarget(animation, target);
                Storyboard.SetTarget(animation, target);
#endif
                Storyboard.SetTargetName(animation, target.GetHashCode().ToString());
                Storyboard.SetTargetProperty(animation, new PropertyPath(targetProperty));
                Storyboard.Children.Add(animation);
            }
            return animation;
        }
        
        public StoryboardBuilder Seek(TimeSpan offset)
        {
            _seekTime = offset;
            return this;
        }

        public StoryboardBuilder Seek(double milliseconds)
        {
            return Seek(TimeSpan.FromMilliseconds(milliseconds));
        }

        public StoryboardBuilder SeekToEnd()
        {
            return SeekToEnd(0);
        }

        public StoryboardBuilder SeekToEnd(int offset)
        {
            var endTime = Storyboard.Children.Any() ? Storyboard.Children.Max(x => x.Duration.TimeSpan.TotalMilliseconds) : 0;
            return Seek(endTime + offset);
        }
    }

    //internal static class StoryboardServices
    //{
    //    public static DependencyObject GetTarget(Timeline timeline)
    //    {
    //        if (timeline == null)
    //            throw new ArgumentNullException("timeline");

    //        return timeline.GetValue(TargetProperty) as DependencyObject;
    //    }

    //    public static void SetTarget(Timeline timeline, DependencyObject value)
    //    {
    //        if (timeline == null)
    //            throw new ArgumentNullException("timeline");

    //        timeline.SetValue(TargetProperty, value);
    //    }

    //    public static readonly DependencyProperty TargetProperty =
    //        DependencyProperty.RegisterAttached(
    //            "Target",
    //            typeof(DependencyObject),
    //            typeof(Timeline),
    //            new PropertyMetadata(null, OnTargetPropertyChanged));

    //    private static void OnTargetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    //    {
    //        Storyboard.SetTarget(d as Timeline, e.NewValue as DependencyObject);
    //    }
    //}
}
